深入探讨如何使用 TypeScript 设计和实施强大、可扩展且类型安全的移动系统。非常适合物流、MaaS 和城市规划技术。
TypeScript 运输优化:移动类型实施全球指南
在现代商业和城市生活这个熙熙攘攘、相互联系的世界中,人员和货物的有效流动至关重要。从在密集的城市景观中穿行的最后一英里交付无人机,到穿越大陆的长途货运卡车,运输方式的多样性已经爆炸式增长。这种复杂性提出了一个重大的软件工程挑战:我们如何构建能够智能地管理、路由和优化如此广泛的移动选项的系统?答案不仅仅在于巧妙的算法,还在于强大而灵活的软件架构。这就是 TypeScript 发光的地方。
本综合指南适用于在物流、移动即服务 (MaaS) 和运输部门工作的软件架构师、工程师和技术主管。我们将探索一种强大、类型安全的方法来建模不同的运输模式——我们称之为“移动类型”——使用 TypeScript。通过利用 TypeScript 的高级类型系统,我们可以创建不仅强大而且可扩展、可维护且显着减少错误的解决方案。我们将从基础概念到实际实施,为您提供构建下一代运输平台的蓝图。
为什么为复杂的运输逻辑选择 TypeScript?
在深入研究实施之前,重要的是要了解 为什么 TypeScript 是该领域如此引人注目的选择。运输逻辑充满了规则、约束和边缘情况。一个简单的错误——例如将货物分配给自行车或将双层巴士安排在低矮的桥下——可能会产生重大的现实后果。TypeScript 提供了一个传统 JavaScript 缺乏的安全网。
- 大规模类型安全: 主要好处是在开发过程中捕获错误,而不是在生产中。通过为“车辆”、“行人”或“公共交通腿”定义严格的合同,您可以防止代码级别的非逻辑操作。例如,编译器可以阻止您访问代表行人移动类型的 fuel_capacity 属性。
 - 增强的开发者体验和协作: 在一个庞大的、全球分布的团队中,清晰且自我记录的代码库至关重要。TypeScript 的接口和类型充当实时文档。具有 TypeScript 支持的编辑器提供智能自动完成和重构工具,大大提高了开发人员的生产力,并使新团队成员更容易理解复杂的领域逻辑。
 - 可扩展性和可维护性: 运输系统不断发展。今天您可能管理汽车和货车;明天可能是电动滑板车、送货无人机和自动驾驶舱。精心设计的 TypeScript 应用程序允许您自信地添加新的移动类型。编译器成为您的指南,指出系统中需要更新的每个部分以处理新类型。这远胜于通过生产错误发现被遗忘的 `if-else` 块。
 - 建模复杂的业务规则: 运输不仅仅是速度和距离。它涉及车辆尺寸、重量限制、道路限制、驾驶员工作时间、通行费成本和环境区域。TypeScript 的类型系统,尤其是像可区分联合和接口这样的功能,提供了一种表达和优雅的方式,可以直接在您的代码中建模这些多方面的规则。
 
核心概念:定义通用移动类型
构建我们的系统的第一步是建立一种通用语言。“移动类型”是什么?它是我们运输网络中可以遍历路径的任何实体的抽象表示。它不仅仅是一辆车;它是一个全面的配置文件,包含路由、调度和优化所需的所有属性。
我们可以从定义大多数(如果不是全部)移动类型共有的核心属性开始。这些属性构成了我们通用模型的基础。
移动类型的关键属性
强大的移动类型应封装以下类别的信息:
- 身份和分类:
        
- `id`:唯一的字符串标识符(例如,“CARGO_VAN_XL”、“CITY_BICYCLE”)。
 - `type`:用于广泛分类的分类器(例如,“VEHICLE”、“MICROMOBILITY”、“PEDESTRIAN”),这对于类型安全的切换至关重要。
 - `name`:人类可读的名称(例如,“超大型货运货车”)。
 
 - 性能配置文件:
        
- `speedProfile`:这可以是简单的平均速度(例如,步行 5 公里/小时),也可以是考虑道路类型、坡度和交通状况的复杂函数。对于车辆,它可能包括加速和减速模型。
 - `energyProfile`:定义能量消耗。这可以模拟燃油效率(升/100 公里或 MPG)、电池容量和消耗(千瓦时/公里),甚至步行和骑自行车的卡路里燃烧。
 
 - 物理约束:
        
- `dimensions`:一个包含 `height`、`width` 和 `length` 的对象,以标准单位(如米)表示。对于检查桥梁、隧道和狭窄街道上的间隙至关重要。
 - `weight`:一个用于 `grossWeight` 和 `axleWeight` 的对象,以千克为单位。对于有重量限制的桥梁和道路至关重要。
 
 - 运营和法律约束:
        
- `accessPermissions`:一个数组或一组标签,定义它可以使用的基础设施类型(例如,['HIGHWAY', 'URBAN_ROAD', 'BIKE_LANE'])。
 - `prohibitedFeatures`:要避免的事项列表(例如,['TOLL_ROADS', 'FERRIES', 'STAIRS'])。
 - `specialDesignations`:特殊分类的标签,例如危险材料的“HAZMAT”或温控货物的“REFRIGERATED”,它们有自己的路由规则。
 
 - 经济模型:
        
- `costModel`:一个定义成本的结构,例如 `costPerKilometer`、`costPerHour`(用于驾驶员工资或车辆磨损)和 `fixedCost`(用于单次旅行)。
 
 - 环境影响:
        
- `emissionsProfile`:一个详细说明排放的对象,例如 `co2GramsPerKilometer`,以实现环保的路由优化。
 
 
TypeScript 中的实际实施策略
现在,让我们将这些概念转化为干净、可维护的 TypeScript 代码。我们将结合使用接口、类型和 TypeScript 最强大的功能之一来进行这种建模: 可区分联合。
步骤 1:定义基本接口
我们将从为我们之前定义的结构化属性创建接口开始。在内部使用标准单位制(如公制)是避免转换错误的全球最佳实践。
示例:基本属性接口
// 所有单位都在内部标准化,例如,米、千克、公里/小时
interface IDimensions {
  height: number;
  width: number;
  length: number;
}
interface IWeight {
  gross: number; // 总重量
  axleLoad?: number; // 可选,用于特定道路限制
}
interface ICostModel {
  perKilometer: number; // 每距离单位的成本
  perHour: number; // 每时间单位的成本
  fixed: number; // 每次旅行的固定成本
}
interface IEmissionsProfile {
  co2GramsPerKilometer: number;
}
接下来,我们创建一个所有移动类型都将共享的基本接口。请注意,许多属性是可选的,因为它们并不适用于每种类型(例如,行人没有尺寸或燃料成本)。
示例:核心 `IMobilityType` 接口
interface IMobilityType {
  id: string;
  name: string;
  averageSpeedKph: number;
  accessPermissions: string[]; // 例如,['PEDESTRIAN_PATH']
  prohibitedFeatures?: string[]; // 例如,['HIGHWAY']
  costModel?: ICostModel;
  emissionsProfile?: IEmissionsProfile;
  dimensions?: IDimensions;
  weight?: IWeight;
}
步骤 2:利用可区分联合进行类型特定的逻辑
可区分联合是一种模式,您在联合中的每种类型上使用文字属性(“区分符”)来允许 TypeScript 缩小您正在使用的特定类型。这非常适合我们的用例。我们将添加一个 `mobilityClass` 属性作为我们的区分符。
让我们为不同的移动类别定义特定的接口。每个接口都将扩展基本 `IMobilityType` 并添加其自己的唯一属性,以及最重要的 `mobilityClass` 区分符。
示例:定义特定的移动接口
interface IPedestrianProfile extends IMobilityType {
  mobilityClass: 'PEDESTRIAN';
  avoidsTraffic: boolean; // 可以使用穿过公园等的捷径。
}
interface IBicycleProfile extends IMobilityType {
  mobilityClass: 'BICYCLE';
  requiresBikeParking: boolean;
}
// 机动车辆的更复杂类型
interface IVehicleProfile extends IMobilityType {
  mobilityClass: 'VEHICLE';
  fuelType: 'GASOLINE' | 'DIESEL' | 'ELECTRIC' | 'HYBRID';
  fuelCapacity?: number; // 以升或千瓦时为单位
  // 使车辆的尺寸和重量成为必需
  dimensions: IDimensions;
  weight: IWeight;
}
interface IPublicTransitProfile extends IMobilityType {
  mobilityClass: 'PUBLIC_TRANSIT';
  agencyName: string; // 例如,“TfL”、“MTA”
  mode: 'BUS' | 'TRAIN' | 'SUBWAY' | 'TRAM';
}
现在,我们将它们组合成一个联合类型。此 `MobilityProfile` 类型是我们系统的基石。任何执行路由或优化的函数都将接受此类型的参数。
示例:最终联合类型
type MobilityProfile = IPedestrianProfile | IBicycleProfile | IVehicleProfile | IPublicTransitProfile;
步骤 3:创建具体的移动类型实例
定义了我们的类型和接口,我们可以创建一个具体的移动配置文件库。这些只是符合我们定义的形状的普通对象。此库可以存储在数据库或配置文件中,并在运行时加载。
示例:具体实例
const WALKING_PROFILE: IPedestrianProfile = {
  id: 'pedestrian_standard',
  name: '步行',
  mobilityClass: 'PEDESTRIAN',
  averageSpeedKph: 5,
  accessPermissions: ['PEDESTRIAN_PATH', 'SIDEWALK', 'PARK_TRAIL'],
  prohibitedFeatures: ['HIGHWAY', 'TUNNEL_VEHICLE_ONLY'],
  avoidsTraffic: true,
  emissionsProfile: { co2GramsPerKilometer: 0 },
};
const CARGO_VAN_PROFILE: IVehicleProfile = {
  id: 'van_cargo_large_diesel',
  name: '大型柴油货车',
  mobilityClass: 'VEHICLE',
  averageSpeedKph: 60,
  accessPermissions: ['HIGHWAY', 'URBAN_ROAD'],
  fuelType: 'DIESEL',
  dimensions: { height: 2.7, width: 2.2, length: 6.0 },
  weight: { gross: 3500 },
  costModel: { perKilometer: 0.3, perHour: 25, fixed: 10 },
  emissionsProfile: { co2GramsPerKilometer: 250 },
};
在路由引擎中应用移动类型
当我们在核心应用程序逻辑(例如路由引擎)中使用这些类型化配置文件时,此架构的真正威力就显现出来了。可区分联合允许我们编写干净、详尽且类型安全的代码来处理不同的移动规则。
假设我们有一个函数需要确定移动类型是否可以遍历道路网络的特定路段(图论术语中的“边”)。此边具有诸如 `maxHeight`、`maxWeight`、`allowedAccessTags` 等属性。
使用详尽的 `switch` 语句进行类型安全逻辑
使用我们的 `MobilityProfile` 类型的函数可以使用 `switch` 语句在 `mobilityClass` 属性上。TypeScript 了解这一点,并将智能地缩小每个 `case` 块中 `profile` 的类型。这意味着在 `'VEHICLE'` 案例中,您可以安全地访问 `profile.dimensions.height` 而无需编译器抱怨,因为它知道它只能是 `IVehicleProfile`。
此外,如果您在 tsconfig 中启用了 `"strictNullChecks": true`,则 TypeScript 编译器将确保您的 `switch` 语句是 详尽的 。如果您向 `MobilityProfile` 联合添加一个新类型(例如 `IDroneProfile`),但忘记为其添加 `case`,编译器将引发错误。对于可维护性来说,这是一个非常强大的功能。
示例:类型安全的可访问性检查函数
// 假设 RoadSegment 是道路段的已定义类型
interface RoadSegment {
  id: number;
  allowedAccess: string[]; // 例如,['HIGHWAY', 'VEHICLE']
  maxHeight?: number;
  maxWeight?: number;
}
function canTraverse(profile: MobilityProfile, segment: RoadSegment): boolean {
  // 基本检查:该段是否允许这种通用类型的访问?
  const hasAccessPermission = profile.accessPermissions.some(perm => segment.allowedAccess.includes(perm));
  if (!hasAccessPermission) {
    return false;
  }
  // 现在,使用可区分联合进行特定检查
  switch (profile.mobilityClass) {
    case 'PEDESTRIAN':
      // 行人几乎没有物理限制
      return true;
    case 'BICYCLE':
      // 自行车可能有一些特定的约束,但这里很简单
      return true;
    case 'VEHICLE':
      // TypeScript 知道 `profile` 在这里是 IVehicleProfile!
      // 我们可以安全地访问 dimensions 和 weight。
      if (segment.maxHeight && profile.dimensions.height > segment.maxHeight) {
        return false; // 对于这座桥/隧道来说太高了
      }
      if (segment.maxWeight && profile.weight.gross > segment.maxWeight) {
        return false; // 对于这座桥来说太重了
      }
      return true;
    case 'PUBLIC_TRANSIT':
      // 公共交通遵循固定路线,因此此检查可能有所不同
      // 现在,我们假设如果它具有基本访问权限,则它是有效的
      return true;
    default:
      // 此默认案例处理详尽性。
      const _exhaustiveCheck: never = profile;
      return _exhaustiveCheck;
  }
}
全球考虑因素和可扩展性
为全球使用而设计的系统必须具有适应性。法规、单位和可用的运输方式在各大洲、国家甚至城市之间差异很大。我们的架构非常适合处理这种复杂性。
处理区域差异
- 计量单位: 全球系统中一个常见的错误来源是公制(公里、千克)和英制(英里、磅)单位之间的混淆。 最佳实践: 在单个单位制(公制是科学和全球标准)上标准化您的整个后端系统。`MobilityProfile` 应该只包含公制值。所有转换为英制单位的转换都应该发生在演示层(API 响应或前端 UI)上,具体取决于用户的区域设置。
 - 当地法规: 货车在伦敦市中心的超低排放区 (ULEZ) 的路线与在德克萨斯州农村的路线非常不同。这可以通过使约束动态化来处理。路由请求可以包括地理环境(例如,`context: 'london_city_center'`),而不是硬编码 `accessPermissions`。然后,您的引擎将应用一组特定于该环境的规则,例如检查车辆的 `fuelType` 或 `emissionsProfile` 是否符合 ULEZ 要求。
 - 动态数据: 您可以通过将基本配置文件与实时数据组合来创建“水合”配置文件。例如,基本 `CAR_PROFILE` 可以与实时交通数据组合,从而为特定时间段的特定路线创建动态 `speedProfile`。
 
使用新的移动类型扩展模型
当您的公司决定推出送货无人机服务时会发生什么?使用此架构,该过程是结构化的且安全的:
- 定义接口: 创建一个新的 `IDroneProfile` 接口,该接口扩展 `IMobilityType` 并包括无人机特定的属性,如 `maxFlightAltitude`、`batteryLifeMinutes` 和 `payloadCapacityKg`。不要忘记区分符:`mobilityClass: 'DRONE';`
 - 更新联合: 将 `IDroneProfile` 添加到 `MobilityProfile` 联合类型:`type MobilityProfile = ... | IDroneProfile;`
 - 遵循编译器错误: 这是神奇的一步。TypeScript 编译器现在将在每个不再详尽的 `switch` 语句中生成错误。它会将您指向像 `canTraverse` 这样的每个函数,并强制您实现 `'DRONE'` 案例的逻辑。此系统过程可确保您不会错过任何关键逻辑,从而大大降低了在引入新功能时出现错误的风险。
 - 实施逻辑: 在您的路由引擎中,添加无人机的逻辑。这将与地面车辆完全不同。它可能涉及检查禁飞区、天气状况(风速)和着陆点可用性,而不是道路网络属性。
 
结论:为未来移动构建基础
优化运输是现代软件工程中最复杂和最具影响力的挑战之一。我们构建的系统必须精确、可靠,并且能够适应快速发展的移动选项环境。通过采用 TypeScript 的强类型,特别是像可区分联合这样的模式,我们可以为这种复杂性构建坚实的基础。
我们概述的移动类型实施提供的不仅仅是代码结构;它提供了一种清晰、可维护且可扩展的思考问题的方式。它将抽象业务规则转化为具体的、类型安全的代码,从而防止错误、提高开发人员的生产力,并使您的平台能够自信地增长。无论您是为全球物流公司构建路由引擎、为主要城市构建多模式行程规划器,还是构建自主车队管理系统,精心设计的类型系统都不是奢侈品——它是成功的必要蓝图。